﻿/*
 * Copyright 2013 Shaun McCloud
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Fibonacci
{
    class Program
    {
		// Need to make this a private variable as the callbacks are what actually write to it
        private static string m_choice;

        static void Main(string[] args)
        {
			// Create the variables we will use
			Stopwatch sw = new Stopwatch();
            UInt64 answer = 0;
			UInt64 fibNumber;
			
			// Create the menu we will use and add items & callbacks to it.
            Menu menu = new Menu();
            menu.Add("Recursion", new MenuCallback(RecursiveCallback));
            menu.Add("For Loop", new MenuCallback(ForLoopCallback));
            menu.Add("While Loop", new MenuCallback(WhileLoopCallback));
            menu.Add("Do While Loop", new MenuCallback(DoWhileLoopCallback));
            menu.Add("Exit", new MenuCallback(ExitCallback));
			// Show the menu
            Console.WriteLine("Which method do you want to use to calculate your Fibonacci number?");
            menu.Show();

			// Only ask for the Fibonacci number they wish to calculate if the did not chose "Exit"
            if (m_choice.ToUpper() != "X")
            {
				// Get the Fibonacci they wish to calculate
                Console.Write("Which Fibonacci number would you like to calculate? ");
                string fibString = Console.ReadLine();
				
				// Only attempt to calculate if the entered a number
                if (UInt64.TryParse(fibString, out fibNumber))
                {
					// Use m_choice to determine which method to use
                    switch (m_choice)
                    {
                        case "A":
                            sw.Start();
                            answer = Recursive(fibNumber);
                            sw.Stop();
                            break;
                        case "B":
                            sw.Start();
                            answer = ForLoop(fibNumber);
                            sw.Stop();
                            break;
                        case "C":
                            sw.Start();
                            answer = WhileLoop(fibNumber);
                            sw.Stop();
                            break;
                        case "D":
                            sw.Start();
                            answer = DoWhileLoop(fibNumber);
                            sw.Stop();
                            break;
                        default:
                            break;
                    }
					// Display the answer and how long it took to calculate in ms
                    Console.WriteLine("Fibonacci number " + fibString + " is " + answer.ToString());
                    Console.WriteLine("It took " + sw.ElapsedMilliseconds + " ms to compute the answer.");
                    Console.Write("Press any key to continue...");
                    Console.ReadKey();
                }
                else
                {
					// Didn't enter a number, show error
                    Console.WriteLine("I'm sorry, but " + fibString + " is not a valid number.");
                    Console.Write("Press any key to continue...");
                    Console.ReadKey();
                }
            }
            else
            {
				// User chose to exit, display a message
                Console.Write("Press any key to continue...");
                Console.ReadKey();
            }
        }

        /// <summary>
        /// If fibNumber is 1, return 0
        /// If fibNumber is 2, return 1
        /// If fibNumber is > 3, call myself recursively until one of the above two occurs, then add
        /// </summary>
        /// <param name="fibNumber">Fibonacci number to calculate</param>
        /// <returns>Fibonacci number as UInt64</returns>
        static UInt64 Recursive(UInt64 fibNumber)
        {
            switch (fibNumber)
            {
                case 1:
                    return 0;
                case 2:
                    return 1;
                default:
                    return Recursive(fibNumber - 1) + Recursive(fibNumber - 2);
            }
        }

        /// <summary>
        /// If fibNumber is 1, return 0
        /// If fibNumber is 2, return 1
        /// If fibNumber is > 3, use a For loop to calculate the Fibonacci number, starting at 3
        /// </summary>
        /// <param name="fibNumber">Fibonacci number to calculate</param>
        /// <returns>Fibonacci number as UInt64</returns>
        static UInt64 ForLoop(UInt64 fibNumber)
        {
            switch (fibNumber)
            {
                case 1:
                    return 0;
                case 2:
                    return 1;
                default:
                    UInt64 temp;
                    UInt64 current = 1;
                    UInt64 last = 0;
                    for (UInt64 i = 3; i < fibNumber + 1; i++)
                    {
						// Copy last number to temp, current to last & then add temp & last to get the new current
                        temp = last;
                        last = current;
                        current = last + temp;
                    }
                    return current;
            }
        }

        /// <summary>
        /// If fibNumber is 1, return 0
        /// If fibNumber is 2, return 1
        /// If fibNumber is > 3, use a While loop to calculate the Fibonacci number, starting at 3
        /// </summary>
        /// <param name="fibNumber">Fibonacci number to calculate</param>
        /// <returns>Fibonacci number as UInt64</returns>
		static UInt64 WhileLoop(UInt64 fibNumber)
		{
            switch (fibNumber)
            {
                case 1:
                    return 0;
                case 2:
                    return 1;
                default:
                    UInt64 temp;
                    UInt64 current = 1;
                    UInt64 last = 0;
                    UInt64 i = 3;
                    while (i < fibNumber + 1)
                    {
                        // Copy last number to temp, current to last & then add temp & last to get the new current, finally increment i
                        temp = last;
                        last = current;
                        current = last + temp;
                        i++;
                    }
                    return current;
            }
		}

        /// <summary>
        /// If fibNumber is 1, return 0
        /// If fibNumber is 2, return 1
        /// If fibNumber is > 3, use a Do While loop to calculate the Fibonacci number, starting at 3
        /// </summary>
        /// <param name="fibNumber">Fibonacci number to calculate</param>
        /// <returns>Fibonacci number as UInt64</returns>
		static UInt64 DoWhileLoop(UInt64 fibNumber)
		{
            switch (fibNumber)
            {
                case 1:
                    return 0;
                case 2:
                    return 1;
                default:
                    UInt64 temp;
                    UInt64 current = 1;
                    UInt64 last = 0;
                    UInt64 i = 3;
                    do
                    {
                        // Copy last number to temp, current to last & then add temp & last to get the new current, finally increment i
                        temp = last;
                        last = current;
                        current = last + temp;
                        i++;
                    }
                    while (i < fibNumber + 1);
                    return current;
            }
		}

        private static void RecursiveCallback()
        {
			// Set m_choice to "A"
            m_choice = "A";
        }

        private static void ForLoopCallback()
        {
			// Set m_choice to "B"
            m_choice = "B";
        }

        private static void WhileLoopCallback()
        {
			// Set m_choice to "C"
            m_choice = "C";
        }

        private static void DoWhileLoopCallback()
        {
			// Set m_choice to "D"
            m_choice = "D";
        }

        private static void ExitCallback()
        {
			// Set m_choice to "X"
            m_choice = "X";
        }
    }
}